package com.meshnetics.gb.type.impl.custom;

import com.meshnetics.gb.type.DataType;
import com.meshnetics.gb.type.DataValue;

import java.util.Map;

/**
 * Structure. Each field must have value specified but null values are acceppted. Values can be accessed by name
 * or index of field.
 *
 * @author AKovylin
 */
public abstract class StructureDataValue extends CustomDataValue {
    private final DataValue[] values;

    protected StructureDataValue(StructureDataType type, DataValue[] values) {
        super(type);
        if (values == null) {
            throw new NullPointerException("values is null");
        }
        if (values.length != type.getFieldCount()) {
            throw new IllegalArgumentException("invalid values length");
        }
        for (int i = 0; i < values.length; i++) {
            if (values[i] != null && !type.getFieldType(i).equals(values[i].getType())) {
                throw new IllegalArgumentException("type mismatch for field " + type.getField(i));
            }
        }
        this.values = (DataValue[]) values.clone();
    }

    protected StructureDataValue(StructureDataType type, Map values) {
        super(type);
        if (values == null) {
            throw new NullPointerException("values is null");
        }
        this.values = new DataValue[type.getFieldCount()];
        for (int i = 0; i < type.getFieldCount(); i++) {
            Object o = values.get(type.getField(i));
            o = o == null ? values.get(new Integer(i)) : o;
            if (o != null && !(o instanceof DataValue)) {
                throw new IllegalArgumentException("type mismatch");
            }
            DataValue dv = (DataValue) o;
            if (!type.getFieldType(i).equals(dv.getType())) {
                throw new IllegalArgumentException("type mismatch");
            }
            this.values[i] = dv;
        }
    }

    public DataValue[] getValues() {
        return (DataValue[]) values.clone();
    }

    public DataValue getFieldValue(String field) {
        return values[((StructureDataType) type).getFieldIndex(field)];
    }

    public DataValue getFieldValue(int index) {
        return values[index];
    }

    protected void setFieldValue(String field, DataValue value) {
        setFieldValue(((StructureDataType) type).getFieldIndex(field), value);
    }

    protected void setFieldValue(int index, DataValue value) {
        if (value != null) {
            DataType fieldType = ((StructureDataType) type).getFieldType(index);
            if (!fieldType.equals(value.getType())) {
                throw new IllegalArgumentException("type mismatch");
            }
        }
        values[index] = value;
    }

    public boolean equals(Object obj) {
        boolean equals = super.equals(obj);
        if (equals) {
            StructureDataValue sdv = (StructureDataValue) obj;
            for (int i = 0; i < values.length; i++) {
                equals = values[i] == null ? sdv.values[i] == null : values[i].equals(sdv.values[i]);
                if (!equals) {
                    break;
                }
            }
        }
        return equals;
    }

    public int hashCode() {
        int hash = super.hashCode();
        for (int i = 0; i < values.length; i++) {
            hash = hash * 29 + (values[i] == null ? 0 : values[i].hashCode());
        }
        return hash;
    }
}
